/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.dbschema;

import java.sql.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Properties;
import java.io.*;
import java.lang.reflect.*;

import org.xmldb.api.base.*;
import org.xmldb.api.modules.*;
import org.xmldb.api.*;
import javax.xml.transform.OutputKeys;
import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory; 
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.DOMException; 
import org.xml.sax.SAXException; 
import org.xml.sax.SAXParseException; 
import org.xml.sax.InputSource;

import org.griphyn.vdl.util.ChimeraProperties;
import org.griphyn.vdl.classes.*;
import org.griphyn.vdl.util.Logging;
import org.griphyn.vdl.parser.*;
import org.griphyn.vdl.router.Cache;
import org.griphyn.vdl.annotation.*;
import edu.isi.pegasus.common.util.Separator;
import org.xml.sax.InputSource;

/**
 * This class provides basic functionalities to interact with the
 * backend database, such as insertion, deletion, and search of
 * entities in the VDC.
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 2079 $ 
 */
public class NXDSchema extends DatabaseSchema implements XDC
{
  /**
   * An instance of the VDLx XML parser.
   */
  private org.griphyn.vdl.parser.VDLxParser m_parser;

  private DocumentBuilderFactory m_factory;

  private DocumentBuilder m_builder;

  /**
   * reference to collection '/db/vdc';
   */
  protected Collection m_db;

  protected Collection m_vdc;

  protected Collection m_meta;
  
  protected CollectionManagementService m_dbColService; 

  protected CollectionManagementService m_vdcColService; 

  protected XPathQueryService m_dbQrySvc;

  protected XPathQueryService m_vdcQrySvc;

  protected XPathQueryService m_metaQrySvc;

  protected XUpdateQueryService m_xupdQrySvc;

  /**
   * A cache for definitions to avoid reloading from the database.
   */
  protected Cache m_cache;  

  /**
   * Instantiates an XML parser for VDLx on demand. Since XML parsing
   * and parser instantiation is an expensive business, the reader will
   * only be generated on demand.
   *
   * @return a valid VDLx parser instance. 
   */
  private org.griphyn.vdl.parser.VDLxParser parserInstance()
  {
    if ( this.m_parser == null ) {
      // obtain the schema location URL from the schema properties: 
      // url is a list of strings representing schema locations. The
      // content exists in pairs, one of the namespace URI, one of the
      // location URL.
      String url = null;
      try {	
        ChimeraProperties props = ChimeraProperties.instance();
        url = 
	  m_dbschemaprops.getProperty( "xml.url", props.getVDLSchemaLocation() );
      } catch (IOException e) {
	Logging.instance().log("nxd", 0, "ignored " + e);  
      }
      this.m_parser = new org.griphyn.vdl.parser.VDLxParser(url);
    }

    // done
    return this.m_parser;
  }

  /**
   * Default constructor for the NXD schema.
   */
  public NXDSchema(String dbDriver) 
    throws ClassNotFoundException, 
	   NoSuchMethodException, InstantiationException, 
	   IllegalAccessException, InvocationTargetException,
	   SQLException, IOException,ParserConfigurationException
  {
    // load the driver from the properties
    super(); // call minimalistic c'tor, no driver loading!
    ChimeraProperties props = ChimeraProperties.instance();

    m_dbschemaprops =
        props.getDatabaseSchemaProperties( PROPERTY_PREFIX );

    m_cache = null;
    m_parser = null;

    // extract those properties specific to the database driver.
    // use default settings.
    String driverPrefix = null;
    String driverName = props.getDatabaseDriverName(driverPrefix);
    Properties driverprops = props.getDatabaseDriverProperties(driverPrefix);
    String url = props.getDatabaseURL(driverPrefix);
    String user = driverprops.getProperty("user", "guest");
    String passwd = driverprops.getProperty("password", "guest");

    try {
	m_factory = DocumentBuilderFactory.newInstance();
	m_builder = m_factory.newDocumentBuilder();


	Class cl = Class.forName(driverName);
	Database database = (Database) cl.newInstance();
	DatabaseManager.registerDatabase(database);

	// get the collection
	m_db = DatabaseManager.getCollection(url + "/db", user, passwd);
	m_dbColService = (CollectionManagementService)
	    m_db.getService("CollectionManagementService", "1.0");
	
	m_vdc = m_db.getChildCollection("vdc");

	if(m_vdc == null) {
	    // collection does not exist, create
	    m_vdc = m_dbColService.createCollection("vdc");
	}
	m_vdc.setProperty(OutputKeys.INDENT, "no");
	
	m_meta = m_db.getChildCollection("metadata");

	if(m_meta == null) {
	    // collection does not exist, create
	    m_meta = m_dbColService.createCollection("metadata");
	}
	m_meta.setProperty(OutputKeys.INDENT, "no");
	
	m_vdcColService = (CollectionManagementService)
	    m_vdc.getService("CollectionManagementService", "1.0");

        m_dbQrySvc = (XPathQueryService) m_db.getService("XPathQueryService", "1.0");

        m_vdcQrySvc = (XPathQueryService) m_vdc.getService("XPathQueryService", "1.0");

        m_metaQrySvc = (XPathQueryService) m_meta.getService("XPathQueryService", "1.0");

        m_dbQrySvc.setProperty("indent", "no");

        m_vdcQrySvc.setProperty("indent", "no");

        m_metaQrySvc.setProperty("indent", "no");

	XUpdateQueryService m_xupdQrySvc = (XUpdateQueryService) m_meta.getService("XUpdateQueryService", "1.0");
	
    } catch (XMLDBException e) {
	throw new SQLException (e.getMessage());
    } 
  }

    private String getDefinitionId(Definition def) {
	String prefix = (def.getType() == Definition.TRANSFORMATION)?"TR_":"DV_";
	String version = def.getVersion();
	String suffix = "";
	if (version != null)
	    suffix = "_" + version;
	return prefix + def.getName() + suffix;
    }

    private String getDefinitionId(String name,
				   String version,
				   int type) {
	String prefix = (type == Definition.TRANSFORMATION)?"TR_":"DV_";
	String suffix = "";
	if (version != null)
	    suffix = "_" + version;
	return prefix + name + suffix;
    }

  //
  // lower level methods, working directly on specific definitions
  //

  /**
   * Loads a single Definition from the backend database into an Java object.
   * This method does not allow wildcarding!
   *
   * @param namespace   namespace, null will be converted into empty string
   * @param name        name, null will be converted into empty string
   * @param version     version, null will be converted into empty string
   * @param type     type of the definition (TR or DV), must not be -1.
   * @return the Definition as specified, or null if not found.
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION
   * @see #saveDefinition( Definition, boolean )
   */
  public Definition 
    loadDefinition( String namespace,
		    String name,
		    String version,
		    int type )
    throws SQLException
  {
    Definition result = null;
    Logging.instance().log("xaction", 1, "START load definition" );

    try {
	Collection col;
	if (namespace != null) 
	    col = m_vdc.getChildCollection(namespace);
	else 
	    col = m_vdc;
	String id = getDefinitionId(name, version, type);
	//Logging.instance().log( "nxd", 0, "Definition id " + id );
	XMLResource res = (XMLResource)col.getResource(id);
	if(res != null) {
	    MyCallbackHandler cb = new MyCallbackHandler();
	    
	    parserInstance().parse( new org.xml.sax.InputSource(new StringReader((String)res.getContent())), cb );
	    result = cb.getDefinition();

	} else {
	    Logging.instance().log( "nxd", 0, "Definition not found" );
	}
    } catch (Exception e) {
	throw new SQLException(e.getMessage());
    }
    Logging.instance().log("xaction", 1, "FINAL load definition" );
    return result;
  }

  /**
   * Saves a Definition, that is either a Transformation or Derivation,
   * into the backend database. This method, of course, does not allow
   * wildcarding. The definition has to be completely specified and
   * valid.
   *
   * @param definition is the new Definition to store.
   * @param overwrite true, if existing defitions will be overwritten by
   * new ones with the same primary (or secondary) key (-set), or false,
   * if a new definition will be rejected on key matches.
   *
   * @return true, if the backend database was changed, or
   *         false, if the definition was not accepted into the backend.
   *
   * @see org.griphyn.vdl.classes.Definition
   * @see org.griphyn.vdl.classes.Transformation
   * @see org.griphyn.vdl.classes.Derivation
   * @see #loadDefinition( String, String, String, int )
   */
  public boolean 
    saveDefinition( Definition definition, 
		    boolean overwrite )
    throws SQLException
  {
      Logging.instance().log( "nxd", 2, "START save definition");

    try {
	String namespace = definition.getNamespace();
	Collection col;
	if (namespace != null)
	    col = m_vdc.getChildCollection(namespace);
	else
	    col = m_vdc;
	String id = getDefinitionId(definition);

	if (col == null) {
	  // collection does not exist, create
	  col = m_vdcColService.createCollection(namespace);
        } else if (!overwrite) {
	  if (col.getResource(id) != null){
	    Logging.instance().log( "app", 0, definition.shortID() +
				    " already exists, ignoring" );
	    return false;
	  }
	}

        // create new XMLResource; an id will be assigned to the new resource
        XMLResource document = (XMLResource)col.createResource(id, "XMLResource");
        document.setContent(definition.toXML("", null));
        col.storeResource(document);

	// add to cache
	//if ( m_cache != null ) m_cache.set( new Long(id), definition );

    } catch (Exception e) {
	throw new SQLException(e.getMessage());
    }
    // done
    Logging.instance().log( "nxd", 2, "FINAL save definition");
    return true;
  }

  /**
   * Search the database for the existence of a definition.
   *
   * @param definition the definition object to search for
   * @return true, if the definition exists, false if not found
   */
  public boolean containsDefinition( Definition definition )
    throws SQLException 
  {
    boolean result = false;
    try {
	String namespace = definition.getNamespace();
	Collection col;
	if (namespace != null)
	    col = m_vdc.getChildCollection(namespace);
	else
	    col = m_vdc;
	String id = getDefinitionId(definition);

	if(col != null) {
	    if (col.getResource(id) != null){
		result = true;
	    }
	}
    } catch ( Exception e ) {
	throw new SQLException(e.getMessage());
    }
    return result;
  }

  /**
   * Delete a specific Definition objects from the database. No wildcard
   * matching will be done. "Fake" definitions are permissable, meaning
   * it just has the secondary key triple.
   *
   * @param definition is the definition specification to delete
   * @return true is something was deleted, false if non existent.
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION 
   */
  public boolean deleteDefinition( Definition definition )
    throws SQLException 
  {
    boolean result = false;

    Logging.instance().log("xaction", 1, "START delete definition" );

    try {
	String namespace = definition.getNamespace();
	Collection col;
	if (namespace != null)
	    col = m_vdc.getChildCollection(namespace);
	else
	    col = m_vdc;
	String id = getDefinitionId(definition);

	if(col != null) {
	    XMLResource res = (XMLResource)col.getResource(id);
	    if (res != null){
		col.removeResource(res);
		result = true;
	    }
	}
    } catch ( Exception e ) {
      // ignore
    }

    Logging.instance().log("xaction", 1, "FINAL delete definition" );
    return result;
  }

  /**
   * Delete Definition objects from the database. This method allows for
   * wildcards in the usual fashion. Use null for strings as wildcards,
   * and -1 for the type wildcard.
   *
   * @param namespace   namespace, null to match any namespace
   * @param name        name, null to match any name
   * @param version     version, null to match any version
   * @param type        definition type (TR or DV)
   * @return            a list containing all Definitions that were deleted
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION 
   */
  public java.util.List deleteDefinition( String namespace,
					  String name,
					  String version,
					  int type )
    throws SQLException 
  {
    Logging.instance().log("xaction", 1, "START delete definitions" );

    java.util.List result = searchDefinition(namespace, name, version, type);
    for (int i=0; i < result.size(); i++)
	deleteDefinition((Definition)result.get(i));

    Logging.instance().log("xaction", 1, "FINAL delete definitions" );
    return result;
  }

  /**
   * Search the database for definitions by ns::name:version triple
   * and by type (either Transformation or Derivation). This version
   * of the search allows for jokers expressed as null value
   *
   * @param namespace   namespace, null to match any namespace
   * @param name        name, null to match any name
   * @param version     version, null to match any version
   * @param type        type of definition (TR/DV, or both)
   * @return            a list of definitions
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION
   */
  public java.util.List searchDefinition( String namespace,
					  String name,
					  String version,
					  int type )
    throws SQLException 
  {
    String xquery = ""; //"declare namespace vdl='http://www.griphyn.org/chimera/VDL';"

    String triple = "";
    if (namespace != null)
	triple += "[@namespace='" + namespace + "']";

    if (name != null)
	triple += "[@name='" + name + "']";

    if (version != null)
	triple += "[@version='" + version + "']";

    if (type != -1) {
	if (type == Definition.TRANSFORMATION)
	    xquery = "//transformation" + triple;
	else
	    xquery += "//derivation" + triple;
    } else
	xquery = "//derivation" + triple + "|//transformation" + triple;

    return searchDefinition(xquery);
  }

  /**
   * Searches the database for all derivations that satisfies a certain query.
   *
   * @param xquery    the query statement
   * @return  a list of definitions
   */
  public java.util.List searchDefinition( String xquery )
    throws SQLException 
  {
    if ( xquery == null )
      throw new NullPointerException("You must specify a query!");

    java.util.List result = new ArrayList();

    try {
	Logging.instance().log("xaction", 1, "query: " + xquery );

	ResourceSet rs = m_dbQrySvc.query(xquery);
	ResourceIterator i = rs.getIterator();
	while(i.hasMoreResources()) {
	    Resource res = i.nextResource();
	    
	    MyCallbackHandler cb = new MyCallbackHandler();
	    
	    parserInstance().parse( new org.xml.sax.InputSource(new StringReader((String)res.getContent())), cb );
	    result.add(cb.getDefinition());
	}

    } catch ( Exception e ) {
	throw new SQLException(e.getMessage());
    }

    return result;
  }

  /**
   * Searches the database for elements that satisfies a certain query.
   *
   * @param xquery    the query statement
   * @return  a list of string
   */
  public java.util.List searchElements( String xquery )
    throws SQLException 
  {
    if ( xquery == null )
      throw new NullPointerException("You must specify a query!");

    java.util.List result = new ArrayList();

    try {
	Logging.instance().log("nxd", 1, "query: " + xquery );

	ResourceSet rs = m_dbQrySvc.query(xquery);
	ResourceIterator i = rs.getIterator();
	while(i.hasMoreResources()) {
	    Resource res = i.nextResource();
	    result.add((String)res.getContent());
	}
    } catch ( Exception e ) {
	throw new SQLException(e.getMessage());
    }
    return result;
  }

  /**
   * Searches the database for annotations that satisfies a certain query.
   *
   * @param xquery    the query statement
   * @return true if found, false otherwise 
   */
  public XMLResource findAnnotation( String xquery )
    throws SQLException 
  {
    if ( xquery == null )
	return null;

    try {
	Logging.instance().log("nxd", 1, "query: " + xquery );

	ResourceSet rs = m_metaQrySvc.query(xquery);
	ResourceIterator i = rs.getIterator();
	if (i.hasMoreResources()) {
	    XMLResource res = (XMLResource)i.nextResource();
	    return res;
	} else 
	    return null;
    } catch ( Exception e ) {
	throw new SQLException(e.getMessage());
    }
  }

  public java.util.List searchFilename( String lfn, int link )
    throws SQLException 
  {
    if ( lfn == null )
      throw new NullPointerException("You must query for a filename");

    String linkQuery = "";
    String type = LFN.toString(link);
    if (type != null)
	linkQuery = "[@link = '" + type + "']";

    String xquery = 
	"//derivation[.//lfn[@file = '" + lfn + "']" + linkQuery + "]";
    java.util.List result = searchDefinition(xquery);

    Logging.instance().log("xaction", 1, "FINAL select LFNs" );
    return result;
  }


  /**
   * Delete one or more definitions from the backend database. The key
   * triple parameters may be wildcards. Wildcards are expressed as
   * <code>null</code> value, or have regular expression.
   *
   * @param namespace   namespace
   * @param name        name
   * @param version     version
   * @param type        definition type (TR or DV)
   * @return            a list of definitions that were deleted.
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION 
   */
  public java.util.List 
    deleteDefinitionEx( String namespace,
		      String name,
		      String version,
		      int type )
    throws SQLException {
    Logging.instance().log("xaction", 1, "START delete definitions ex" );

    java.util.List result = searchDefinitionEx(namespace, name, version, type);
    for (int i=0; i < result.size(); i++)
	deleteDefinition((Definition)result.get(i));

    Logging.instance().log("xaction", 1, "FINAL delete definitions ex" );
    return result;
  }

  /**
   * Searches the database for definitions by ns::name:version triple
   * and by type (either Transformation or Derivation). This version of
   * the search allows for jokers expressed as null value
   *
   * @param namespace   namespace, null to match any namespace
   * @param name        name, null to match any name
   * @param version     version, null to match any version
   * @param type        type of definition, see below, or -1 as wildcard
   * @return            a list of Definition items, which may be empty
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION
   * @see #loadDefinition( String, String, String, int )
   */
  public java.util.List 
    searchDefinitionEx( String namespace,
		      String name,
		      String version,
		      int type )
    throws SQLException {
    String xquery = ""; 

    String triple = "";
    if (namespace != null)
	triple += "[matches(@namespace, '" + namespace + "')]";

    if (name != null)
	triple += "[matches(@name, '" + name + "')]";

    if (version != null)
	triple += "[matches(@version, '" + version + "')]";

    if (type != -1) {
	if (type == Definition.TRANSFORMATION)
	    xquery = "//transformation" + triple;
	else
	    xquery += "//derivation" + triple;
    } else
	xquery = "//derivation" + triple + "|//transformation" + triple;
    return searchDefinition(xquery);
  }

  /**
   * Searches the database for all LFNs that match a certain pattern.
   * The linkage is an additional constraint. This method allows
   * regular expression
   *
   * @param lfn    the LFN name
   * @param link   the linkage type of the LFN
   * @return       a list of filenames that match the criterion.
   *
   * @see org.griphyn.vdl.classes.LFN#NONE
   * @see org.griphyn.vdl.classes.LFN#INPUT
   * @see org.griphyn.vdl.classes.LFN#OUTPUT
   * @see org.griphyn.vdl.classes.LFN#INOUT
   */
  public java.util.List 
    searchLFN( String lfn, int link )
    throws SQLException {
    if ( lfn == null )
      throw new NullPointerException("You must query for a filename");

    String linkQuery = "";
    String type = LFN.toString(link);
    if (type != null)
	linkQuery = "[@link = '" + type + "')]";

    String xquery = 
	//	"//lfn[matches(@file, '" + lfn + "')]" + linkQuery + "/@file";
	"//lfn" + linkQuery + "/@file[matches(.,  '" + lfn + "')]";
    java.util.List result = searchElements(xquery);

    Logging.instance().log("xaction", 1, "FINAL select LFNs" );
    return result;
  }

  /**
   * Searches the database for a list of namespaces of the definitions
   * Sorted in ascending order.
   *
   * @param type   type of definition, see below, or -1 for both
   * @return       a list of namespaces 
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION
   */
  public java.util.List 
    getNamespaceList( int type )
    throws SQLException {

    String xquery = "";
    if (type == Definition.TRANSFORMATION)
	xquery = "for $n in distinct-values(//transformation/@namespace) order by $n return $n"; 
    else if (type == Definition.DERIVATION)
	xquery = "for $n in distinct-values(//derivation/@namespace) order by $n return $n";
    else
       xquery = "for $n in distinct-values(//derivation/@namespace|//transformation/@namespace) order by $n return $n";

    java.util.List result = searchElements(xquery);

    Logging.instance().log("xaction", 1, "FINAL select LFNs" );
    return result;
  }

  /**
   * Searches the database for a list of fully-qualified names of 
   * the definitions sorted in ascending order.
   *
   * @param type   type of definition, see below, or -1 for both.
   * @return       a list of FQDNs 
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION
   */
  public java.util.List 
    getFQDNList( int type )
    throws SQLException {
    String xquery = "";

    if (type == Definition.TRANSFORMATION)
	xquery = "for $d in //transformation order by $d/@namespace empty least, $d/@name, $d/@version return string-join((string-join(($d/@namespace, $d/@name), '::'), $d/@version), ':')";
    else if (type == Definition.DERIVATION)
	xquery = "for $d in //derivation order by $d/@namespace empty least, $d/@name, $d/@version return string-join((string-join(($d/@namespace, $d/@name), '::'), $d/@version), ':')";
    else
	xquery = "for $d in (//transformation|//derivation) order by $d/@namespace empty least, $d/@name, $d/@version return string-join((string-join(($d/@namespace, $d/@name), '::'), $d/@version), ':')";

    java.util.List result = searchElements(xquery);

    Logging.instance().log("xaction", 1, "FINAL select LFNs" );
    return result;
  }

  /**
   * Deletes an annotation with the specified key.
   *
   * @param primary is the primary object specifier for the class. 
   * According to the type, this is either the FQDI, or the filename.
   * @param secondary is a helper argument for annotations to calls
   * and formal arguments, and should be null for all other classes.
   * For calls, the argument must be packed into {@link java.lang.Integer}.
   * @param kind defines the kind/class of object to annotate.
   * @param key is the annotation key.
   * @return true, if the database was modified, false otherwise.
   * @exception SQLException, if something went wrong during database
   * access.
   */
  public boolean deleteAnnotation( String primary, Object secondary,
				   int kind, String key )
    throws SQLException, IllegalArgumentException
  {
    String subject = "";
    String select = null;

    switch ( kind ) {
    case CLASS_TRANSFORMATION:
      subject = "tr";
      break;
    case CLASS_DERIVATION:
      subject = "dv";
      break;
    case CLASS_CALL:
      // may throw ClassCastException
      subject = "tr";	
      select = "call[" +  ((Integer) secondary).intValue() + "]";
      break;
    case CLASS_DECLARE:
      subject = "tr";
      // may throw ClassCastException
      //select = "declare[@name='" + (String)secondary + "']";
      select = (String)secondary;
      break;
    case CLASS_FILENAME:
      subject = "lfn";
      break;
    default:
      throw new IllegalArgumentException( "The class kind=" + kind + " cannot be annotated" );
    }

    try {
    XMLResource res = null;
    String xquery = "/annotation/metadata[@subject=\"" + subject + "\"][@name=\"" +
	primary + "\"]";
    if (select == null) {
	if (kind != CLASS_FILENAME) {
	    xquery += "[empty(@select)]";
	}
    } else {
	xquery += "[@select=\"" + select + "\"]";
    }

    xquery +=  "/attribute[@name=\"" + key + "\"]";

    if ((res = findAnnotation(xquery)) != null) {
	String id = res.getDocumentId();

	// get the document
	XMLResource document = (XMLResource)m_meta.getResource(id);
	m_meta.removeResource(document);
	return true;
    }
    return false;
    } catch (XMLDBException e) {
      throw new SQLException(e.getMessage());
    }
  }

  /**
   * Deletes a specific key in an annotated transformation.
   *
   * @param fqdi is the FQDI of the transformation
   * @param key is the key to search for
   * @return true, if the database was modified, false otherwise.
   * @see org.griphyn.vdl.classes.Transformation
   */
  public boolean deleteAnnotationTransformation( String fqdi, String key )
    throws SQLException, IllegalArgumentException
  {
    return deleteAnnotation(fqdi, null, CLASS_TRANSFORMATION, key);
  }

  /**
   * Deletes a specific key in an annotated derivation.
   *
   * @param fqdi is the FQDI of the derivation
   * @param key is the key to search for
   * @return true, if the database was modified, false otherwise.
   * @see org.griphyn.vdl.classes.Derivation
   */
  public boolean deleteAnnotationDerivation( String fqdi, String key )
    throws SQLException, IllegalArgumentException
  {
    return deleteAnnotation(fqdi, null, CLASS_DERIVATION, key);
  }   

  /**
   * Deletes a specific key in an annotated formal argument.
   *
   * @param fqdi is the FQDI of the transformation
   * @param farg is the name of the formal argument
   * @param key is the key to search for
   * @return true, if the database was modified, false otherwise.
   * @see org.griphyn.vdl.classes.Declare
   */
  public boolean deleteAnnotationDeclare( String fqdi, String farg,
				      String key )
    throws SQLException, IllegalArgumentException
  {
    return deleteAnnotation(fqdi, farg, CLASS_DECLARE, key);
  }

  /**
   * Deletes a specific key for a call statement.
   *
   * @param fqdi is the FQDI of the transformation
   * @param index is the number of the call to annotate.
   * @param key is the key to search for
   * @return true, if the database was modified, false otherwise.
   * @see org.griphyn.vdl.classes.Call
   */
  public boolean deleteAnnotationCall( String fqdi, int index, 
				   String key )
    throws SQLException, IllegalArgumentException
  {
    return deleteAnnotation(fqdi, new Integer(index), CLASS_CALL, key);
  }

  /**
   * Deletes a specific key in an annotated filename.
   *
   * @param filename is the name of the file that was annotated.
   * @param key is the key to search for
   * @return true, if the database was modified, false otherwise.
   * @see org.griphyn.vdl.classes.LFN
   */
  public boolean deleteAnnotationFilename( String filename, String key )
    throws SQLException, IllegalArgumentException
  {
    return deleteAnnotation(filename, null, CLASS_FILENAME, key);
  }

  /**
   * Annotates a transformation with a tuple.
   *
   * @param fqdi is the FQDI to annotate
   * @param annotation is the value to place
   * @param overwrite is a predicate on replace or maintain.
   * @return the insertion id, or -1, if the database was untouched
   * @see org.griphyn.vdl.classes.Transformation
   */
  public long saveAnnotationTransformation( String fqdi, 
					    Tuple annotation, 
					    boolean overwrite )
    throws SQLException, IllegalArgumentException
  {
    /* 
    try {
     String key = annotation.getKey();
     String type = annotation.getTypeString();
     Object value = annotation.getValue();

     Logging.instance().log( "nxd", 2, "INSERT INTO anno_tr" );

     String id = null;
     XMLResource res = null;

     String xupdate = 
	 "<xu:modifications version=\"1.0\" " +
	 "xmlns:xu=\"http://www.xmldb.org/xupdate\">";
     String xquery = "//annotation/metadata[@subject='tr'][@name='" +
	  fqdi + "']";
     if ((res = findAnnotation(xquery)) != null) {
	 //annotation for tr exists
	 id = res.getDocumentId();
	 String xquery_attr = xquery + "/attribute[@name='" + key + "']/text()";
	 if (findAnnotation(xquery_attr)!=null) {
	     //attribute already exists
	     Logging.instance().log( "nxd", 2, "Attribute already exists." );

	     if (!overwrite) 
		 return -1;
	     xupdate += "<xu:update select=\"" + xquery + "\">" +
		 value + "</xu:update>";
	     //xupdate += "<xu:update select=\"" + xquery_attr + "/@type\">" + 
	     //type + "</xu:update>";
	 } else {
	     //attribute does not exist
	     Logging.instance().log( "nxd", 2, "Attribute does not exist." );

	     xupdate += "<xu:append select=\"" + xquery + "\">" +
		 "<xu:element name=\"attribute\">" +
		 "<xu:attribute name=\"name\">" + key + "</xu:attribute>" +
		 "<xu:attribute name=\"type\">" + type + "</xu:attribute>" +
		 value + "</xu:element>" +
		 "</xu:append>";
	 }
	 xupdate += "</xu:modifications>";
	 System.out.println(xupdate);
	 long l = m_xupdQrySvc.update(xupdate);
     } else {
	 //create the annotation
	 String anno = "<annotation><metadata subject=\"tr\" name=\"" + fqdi + "\">" +
	     "<attribute name=\"" + key + "\" type=\"" + type + "\">" + value + "</attribute>" +
	     "</metadata></annotation>";

	 // create new XMLResource; an id will be assigned to the new resource
	 XMLResource document = (XMLResource)m_meta.createResource(null, "XMLResource");
	 document.setContent(anno);
	 m_meta.storeResource(document);
     }
     return 0; 
    } catch (XMLDBException e) {
	throw new SQLException(e.getMessage());
    }
    */
    return saveAnnotation(fqdi, null, CLASS_TRANSFORMATION, annotation, overwrite);
  }

  /**
   * Annotates a derivation with a tuple.
   *
   * @param fqdi is the FQDI to annotate
   * @param annotation is the value to place
   * @param overwrite is a predicate on replace or maintain.
   * @return the insertion id, or -1, if the database was untouched
   * @see org.griphyn.vdl.classes.Derivation
   */
  public long saveAnnotationDerivation( String fqdi, 
					Tuple annotation, 
					boolean overwrite )
    throws SQLException, IllegalArgumentException
  {
    return saveAnnotation(fqdi, null, CLASS_DERIVATION, annotation, overwrite);
  }

  /**
   * Annotates a transformation argument with a tuple.
   *
   * @param fqdi is the FQDI to annotate
   * @param formalname is the name of the formal argument to annotoate.
   * @param annotation is the value to place
   * @param overwrite is a predicate on replace or maintain.
   * @return the insertion id, or -1, if the database was untouched
   * @see org.griphyn.vdl.classes.Declare
   */
  public long saveAnnotationDeclare( String fqdi, 
				     String formalname,
				     Tuple annotation, 
				     boolean overwrite )
    throws SQLException, IllegalArgumentException
  {
    return saveAnnotation(fqdi, formalname, CLASS_DECLARE, annotation, overwrite);
  }

  /**
   * Annotates a transformation call with a tuple.
   *
   * @param fqdi is the FQDI to annotate
   * @param index is the number of the call to annotate.
   * @param annotation is the value to place
   * @param overwrite is a predicate on replace or maintain.
   * @return the insertion id, or -1, if the database was untouched
   * @see org.griphyn.vdl.classes.Call
   */
  public long saveAnnotationCall( String fqdi, 
				  int index,
				  Tuple annotation, 
				  boolean overwrite )
    throws SQLException, IllegalArgumentException
  {
    return saveAnnotation(fqdi, new Integer(index), CLASS_CALL, annotation, overwrite);
  }

  /**
   * Annotates a logical filename with a tuple.
   *
   * @param filename is the FQDI to annotate
   * @param annotation is the value to place
   * @param overwrite is a predicate on replace or maintain.
   * @return the insertion id, or -1, if the database was untouched
   * @see org.griphyn.vdl.classes.LFN
   */
  public long saveAnnotationFilename( String filename, 
				      Tuple annotation, 
				      boolean overwrite )
    throws SQLException, IllegalArgumentException
  {
    return saveAnnotation(filename, null, CLASS_FILENAME, annotation, overwrite);
  }

  /**
   * Annotates any of the annotatable classes with the specified tuple.
   * This is an interface method to the various class-specific methods.
   *
   * @param primary is the primary object specifier for the class. 
   * According to the type, this is either the FQDI, or the filename.
   * @param secondary is a helper argument for annotations to calls
   * and formal arguments, and should be null for all other classes.
   * For calls, the argument must be packed into {@link java.lang.Integer}.
   * @param kind defines the kind/class of object to annotate.
   * @param annotation is the value to place into the class.
   * @param overwrite is a predicate on replace or maintain.
   * @return the insertion id, or -1, if the database was untouched
   * @see #saveAnnotationTransformation( String, Tuple, boolean )
   * @see #saveAnnotationDerivation( String, Tuple, boolean )
   * @see #saveAnnotationCall( String, int, Tuple, boolean )
   * @see #saveAnnotationDeclare( String, String, Tuple, boolean )
   * @see #saveAnnotationFilename( String, Tuple, boolean )
   */
  public long saveAnnotation( String primary, Object secondary,
			      int kind, Tuple annotation, boolean overwrite )
    throws SQLException, IllegalArgumentException
  {
    long result = -1;
    String subject = "";
    String select = null;
    String q_sec = null;
    String defn = "transformation";

    switch ( kind ) {
    case CLASS_TRANSFORMATION:
      subject = "tr";
      break;
    case CLASS_DERIVATION:
      subject = "dv";
      defn = "derivation";
      break;
    case CLASS_CALL:
      // may throw ClassCastException
      subject = "tr";	
      select = "call[" +  ((Integer) secondary).intValue() + "]";
      q_sec = select;
      break;
    case CLASS_DECLARE:
      subject = "tr";
      // may throw ClassCastException
      q_sec = "declare[@name='" + (String)secondary + "']";
      select = (String)secondary;
      break;
    case CLASS_FILENAME:
      subject = "lfn";
      break;
    default:
      throw new IllegalArgumentException( "The class kind=" + kind + " cannot be annotated" );
    }

    try {

      if (kind != CLASS_FILENAME) {
	String[] names = Separator.split(primary);
	String q_ns, q_name, q_ver;

	if (names[0] == null)
	  q_ns = "[empty(@namespace)]";
	else
	  q_ns = "[@namespace='" + names[0] + "']";

	if (names[1] == null)
	  q_name = "[empty(@name)]";
	else
	  q_name = "[@name='" + names[1] + "']";

	if (names[2] == null)
	  q_ver = "[empty(@version)]";
	else
	  q_ver = "[@version='" + names[2] + "']";

	//check if tr/dv is valid
	String xquery = "//" + defn + q_ns + q_name + q_ver;
	if (q_sec != null )
	  xquery += "/" + q_sec;
	Logging.instance().log("nxd", 0, "query: " + xquery);
	ResourceSet rs = m_vdcQrySvc.query(xquery);
	ResourceIterator i = rs.getIterator();
	if (!i.hasMoreResources()) {
	  Logging.instance().log("app", 0, "definition not found!");
	  return -1;
	}
      }

      String key = annotation.getKey();
      String type = annotation.getTypeString();
      Object value = annotation.getValue();
    
      String id = null;
      XMLResource res = null;
      String xquery = "/annotation/metadata[@subject=\"" + subject + "\"][@name=\"" +
	primary + "\"]";
      if (select == null) {
	if (kind != CLASS_FILENAME) {
	  xquery += "[empty(@select)]";
	}
      } else {
	xquery += "[@select=\"" + select + "\"]";
      }

      xquery +=  "/attribute[@name=\"" + key + "\"]";
	    
      if ((res = findAnnotation(xquery)) != null) {
	if (!overwrite) {
	  System.err.println("key " + key + " already defined!");
	  return -1;
	}
	id = res.getDocumentId();
      }
	    
      //create the annotation
      String anno = "<annotation xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"><metadata subject=\"" + subject + "\" name=\"" + primary + "\"";
      if (select !=null)
	anno += " select=\"" + select + "\"";
      anno += ">" + "<attribute name=\"" + key + "\" xsi:type=\"xs:" + type + "\">" + value + "</attribute>" +
	"</metadata></annotation>";
	
      // create new XMLResource; an id will be assigned to the new resource
      XMLResource document = (XMLResource)m_meta.createResource(id, "XMLResource");
	
      document.setContent(anno);
      m_meta.storeResource(document);
      
      return 0;
    } catch (XMLDBException e) {
      throw new SQLException(e.getMessage());
    }
  }

  /**
   * Obtains the value to a specific key in an annotated transformation.
   *
   * @param fqdi is the FQDI of the transformation
   * @param key is the key to search for
   * @return the annotated value, or null if not found.
   * @see org.griphyn.vdl.classes.Transformation
   */
  public Tuple loadAnnotationTransformation( String fqdi, String key )
    throws SQLException, IllegalArgumentException
  {
      return loadAnnotation(fqdi, null, CLASS_TRANSFORMATION, key);
  }

  /**
   * Obtains the value to a specific key in an annotated derivation.
   *
   * @param fqdi is the FQDI of the derivation
   * @param key is the key to search for
   * @return the annotated value, or null if not found.
   * @see org.griphyn.vdl.classes.Derivation
   */
  public Tuple loadAnnotationDerivation( String fqdi, String key )
    throws SQLException, IllegalArgumentException
  {
      return loadAnnotation(fqdi, null, CLASS_DERIVATION, key);
  }

  /**
   * Obtains the value to a specific key in an annotated formal argument.
   *
   * @param fqdi is the FQDI of the transformation
   * @param farg is the name of the formal argument
   * @param key is the key to search for
   * @return the annotated value, or null if not found
   * @see org.griphyn.vdl.classes.Declare
   */
  public Tuple loadAnnotationDeclare( String fqdi, String farg,
				      String key )
    throws SQLException, IllegalArgumentException
  {
      return loadAnnotation(fqdi, farg, CLASS_DECLARE, key);
  }

  /**
   * Obtains the value to a specific key for a call statement.
   *
   * @param fqdi is the FQDI of the transformation
   * @param index is the number of the call to annotate.
   * @param key is the key to search for
   * @return the annotated value, or null if not found
   * @see org.griphyn.vdl.classes.Call
   */
  public Tuple loadAnnotationCall( String fqdi, int index, 
				   String key )
    throws SQLException, IllegalArgumentException
  {
      return loadAnnotation(fqdi, new Integer(index), CLASS_CALL, key);
  }

  /**
   * Obtains the value to a specific key in an annotated filename.
   *
   * @param filename is the name of the file that was annotated.
   * @param key is the key to search for
   * @return the annotated value, or null if not found.
   * @see org.griphyn.vdl.classes.LFN
   */
  public Tuple loadAnnotationFilename( String filename, String key )
    throws SQLException, IllegalArgumentException
  {
      return loadAnnotation(filename, null, CLASS_FILENAME, key);
  }

  /**
   * Retrieves a specific annotation from an annotatable classes with
   * the specified tuple. This is an interface method to the various
   * class-specific methods.
   *
   * @param primary is the primary object specifier for the class. 
   * According to the type, this is either the FQDI, or the filename.
   * @param secondary is a helper argument for annotations to calls
   * and formal arguments, and should be null for all other classes.
   * For calls, the argument must be packed into {@link java.lang.Integer}.
   * @param kind defines the kind/class of object to annotate.
   * @param key is the key to look for.
   * @return null if not found, otherwise the annotation tuple.
   * @see #loadAnnotationTransformation( String, String )
   * @see #loadAnnotationDerivation( String, String )
   * @see #loadAnnotationCall( String, int, String )
   * @see #loadAnnotationDeclare( String, String, String )
   * @see #loadAnnotationFilename( String, String )
   */
  public Tuple loadAnnotation( String primary, Object secondary,
			       int kind, String key )
    throws SQLException, IllegalArgumentException
  {
    Tuple result = null;
    String subject = "";
    String select = null;

    switch ( kind ) {
    case CLASS_TRANSFORMATION:
      subject = "tr";
      break;
    case CLASS_DERIVATION:
      subject = "dv";
      break;
    case CLASS_CALL:
      // may throw ClassCastException
      subject = "tr";	
      select = "call[" +  ((Integer) secondary).intValue() + "]";
      break;
    case CLASS_DECLARE:
      subject = "tr";
      // may throw ClassCastException
      //select = "declare[@name='" + (String)secondary + "']";
      select = (String)secondary;
      break;
    case CLASS_FILENAME:
      subject = "lfn";
      break;
    default:
      throw new IllegalArgumentException( "The class kind=" + kind + " cannot be annotated" );
    }

    try {
    String id = null;
    String xquery = "/annotation/metadata[@subject=\"" + subject + "\"][@name=\"" +
	primary + "\"]";
    if (select == null) {
	if (kind != CLASS_FILENAME) {
	    xquery += "[empty(@select)]";
	}
    } else {
	xquery += "[@select=\"" + select + "\"]";
    }

    xquery +=  "/attribute[@name=\"" + key + "\"]";

    XMLResource res = null;
    if ((res = findAnnotation(xquery)) != null) {
	result = loadAnnotationResource(res);
    }
    return result;
    } catch (Exception e) {
	throw new SQLException(e.getMessage());
    }
  }

  /**
   * get the annotation from a XML resource
   */
  protected Tuple loadAnnotationResource(XMLResource res)  
    throws SQLException
  {
      Tuple result = null;

      if (res == null) 
	  return result;

      Element elem;
      try {
	  elem = (Element)res.getContentAsDOM();
      } catch (Exception e) {
	  throw new SQLException( e.getMessage());
      }
      if (elem != null) {
	  String key = elem.getAttribute("name");
	  String type = elem.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance","type");
	  String value = elem.getFirstChild().getNodeValue();
	  if (key == null || type == null || value == null)
	      return result;
	  if (type.equals("xs:string")) {
	      result = new TupleString(key, null);
	      result.setValue(value);
	      return result;
	  }
	  if (type.equals("xs:float")){
	      result = new TupleFloat(key, 0);
	      result.setValue(value);
	      return result;
	  }
	  if (type.equals("xs:int")){
	      result = new TupleInteger(key, 0);
	      result.setValue(value);
	      return result;
	  }
	  if (type.equals("xs:boolean")) {
	      result = new TupleBoolean(key, false);
	      result.setValue(value);
	      return result;
	  }
	  if (type.equals("xs:date")) {
	      result = new TupleDate(key, null);
	      result.setValue(value);
	      return result;
	  }
      }
      return result;
  }

  /**
   * Lists all annotations for a transformation.
   *
   * @param fqdi is the FQDI of the transformation
   * @return a list of tuples, which may be empty.
   * @see org.griphyn.vdl.classes.Transformation
   */
  public java.util.List loadAnnotationTransformation( String fqdi )
    throws SQLException, IllegalArgumentException
  {
      return loadAnnotation(fqdi, null, CLASS_TRANSFORMATION);
  }

  /**
   * Lists all annotations for a derivation.
   *
   * @param fqdi is the FQDI of the derivation
   * @return a list of tuples, which may be empty.
   * @see org.griphyn.vdl.classes.Derivation
   */
  public java.util.List loadAnnotationDerivation( String fqdi )
    throws SQLException, IllegalArgumentException
  {
      return loadAnnotation(fqdi, null, CLASS_DERIVATION);
  }


  /**
   * Lists all annotations for a formal argument.
   *
   * @param fqdi is the FQDI of the transformation
   * @param farg is the name of the formal argument
   * @return a list of tuples, which may be empty.
   * @see org.griphyn.vdl.classes.Declare
   */
  public java.util.List loadAnnotationDeclare( String fqdi, String farg )
    throws SQLException, IllegalArgumentException
  {
      return loadAnnotation(fqdi, farg, CLASS_DECLARE);
  }

  /**
   * Lists all annotations for a call statement.
   *
   * @param fqdi is the FQDI of the transformation
   * @param index is the number of the call to annotate.
   * @return a list of tuples, which may be empty.
   * @see org.griphyn.vdl.classes.Call
   */
  public java.util.List loadAnnotationCall( String fqdi, int index )
    throws SQLException, IllegalArgumentException
  {
      return loadAnnotation(fqdi, new Integer(index), CLASS_CALL);
  }

  /**
   * Lists all annotations for a logical filename.
   *
   * @param filename is the logical filename.
   * @return a list of tuples, which may be empty.
   * @see org.griphyn.vdl.classes.LFN
   */
  public java.util.List loadAnnotationFilename( String filename )
    throws SQLException, IllegalArgumentException
  {
      return loadAnnotation(filename, null, CLASS_FILENAME);
  }

  /**
   * Retrieves all annotations from an annotatable classes with
   * the specified tuple. This is an interface method to the various
   * class-specific methods.
   *
   * @param primary is the primary object specifier for the class. 
   * According to the type, this is either the FQDI, or the filename.
   * @param secondary is a helper argument for annotations to calls
   * and formal arguments, and should be null for all other classes.
   * For calls, the argument must be packed into {@link java.lang.Integer}.
   * @param kind defines the kind/class of object to annotate.
   *
   * @return null if not found, otherwise the annotation tuple.
   * @see #loadAnnotationTransformation( String )
   * @see #loadAnnotationDerivation( String )
   * @see #loadAnnotationCall( String, int )
   * @see #loadAnnotationDeclare( String, String )
   * @see #loadAnnotationFilename( String )
   */
  public java.util.List loadAnnotation( String primary, 
					Object secondary,
					int kind )
    throws SQLException, IllegalArgumentException
  {
    java.util.List result = new java.util.ArrayList();
    String subject = "";
    String select = null;

    switch ( kind ) {
    case CLASS_TRANSFORMATION:
      subject = "tr";
      break;
    case CLASS_DERIVATION:
      subject = "dv";
      break;
    case CLASS_CALL:
      // may throw ClassCastException
      subject = "tr";	
      select = "call[" +  ((Integer) secondary).intValue() + "]";
      break;
    case CLASS_DECLARE:
      subject = "tr";
      // may throw ClassCastException
      //select = "declare[@name='" + (String)secondary + "']";
      select = (String)secondary;
      break;
    case CLASS_FILENAME:
      subject = "lfn";
      break;
    default:
      throw new IllegalArgumentException( "The class kind=" + kind + " cannot be annotated" );
    }

    try {
    String id = null;
    String xquery = "/annotation/metadata[@subject=\"" + subject + "\"][@name=\"" +
	primary + "\"]";
    if (select == null) {
	if (kind != CLASS_FILENAME) {
	    xquery += "[empty(@select)]";
	}
    } else {
	xquery += "[@select=\"" + select + "\"]";
    }

    xquery +=  "/attribute";
    Logging.instance().log("nxd", 1, "query: " + xquery );

    ResourceSet rs = m_metaQrySvc.query(xquery);
    ResourceIterator i = rs.getIterator();
    while (i.hasMoreResources()) {
	XMLResource res = (XMLResource)i.nextResource();
	Tuple tuple = loadAnnotationResource(res);
	if (tuple != null) {
	    result.add(tuple);
	}
    }
    return result;
    } catch (Exception e) {
	throw new SQLException(e.getMessage());
    }
  }


  /**
   * Search for LFNs or Definitions that has certain annotations
   *
   * @param kind defines the kind/class of object annotated.
   * @param arg is used only for TR ARG and TR CALL. For the former
   * it is the name of the argument (String), for the latter the position of 
   * the call (Integer).
   * @param tree stores the query tree to query the annotation
   * @return a list of LFNs if search for filenames, otherwise a list of
   * definitions.
   * @exception SQLException if something goes wrong with the database.
   * @see org.griphyn.vdl.annotation.QueryTree
   */
  public java.util.List searchAnnotation( int kind,
					  Object arg,
					  QueryTree tree)
    throws SQLException
  {
    java.util.List result = new java.util.ArrayList();

    if ( tree == null)
	return result;

    String subject = "";
    String defn = "transformation";
    String select = null;

    switch ( kind ) {
    case CLASS_TRANSFORMATION:
      subject = "tr";
      break;
    case CLASS_DERIVATION:
      subject = "dv";
      defn = "derivation";
      break;
    case CLASS_CALL:
      // may throw ClassCastException
      subject = "tr";	
      select = "call[" +  ((Integer) arg).intValue() + "]";
      break;
    case CLASS_DECLARE:
      subject = "tr";
      // may throw ClassCastException
      //select = "declare[@name='" + (String)arg + "']";
      select = (String)arg;
      break;
    case CLASS_FILENAME:
      subject = "lfn";
      break;
    default:
      throw new IllegalArgumentException( "The class kind=" + kind + " cannot be annotated" );
    }

    try {
    String id = null;
    String cond = "[@subject=\"" + subject + "\"]";
    if (select == null) {
	if (kind != CLASS_FILENAME) {
	    cond += "[empty(@select)]";
	}
    } else {
	cond += "[@select=\"" + select + "\"]";
    }
    String xquery = "for $mn in distinct-values(//annotation/metadata" + cond + "/@name) " +
	"let $m := //annotation/metadata[@name=$mn]" + cond; 

    String where = " where ";
    where += tree.toXQuery("$m/attribute");

    if (kind == CLASS_FILENAME) {
	xquery += ", $r := $m";
	xquery += where;
	xquery += " return $mn";

	return searchElements(xquery);
    } else {
	xquery += ", $n := substring-before($mn, '::'), $na := substring-after($mn, '::'), $iv := if ($na) then $na else $mn, $v := substring-after($iv, ':'), $ib := substring-before($iv, ':'), $i := if ($ib) then $ib else $iv,";
	xquery += " $t := if ($n) then if ($v) then //" + defn + "[@namespace=$n][@name=$i][@version=$v] else //" + defn + "[@namespace=$n][@name=$i][empty(@version)] else if ($v) then //" + defn + "[empty(@namespace)][@name=$i][@version=$v] else //" + defn + "[empty(@namespace)][@name=$i][empty(@version)]";
	
	xquery += where;
	if (kind == CLASS_DECLARE)
	    xquery += " return $t[" + "declare[@name='" + select + "']" + "]";
	else
	    xquery += " return $t";
	return searchDefinition(xquery);
    }

    } catch (Exception e) {
	throw new SQLException(e.getMessage());
    }
  }


  /**
   * pass-thru to driver.
   * @return true, if it is feasible to cache results from the driver
   * false, if requerying the driver is sufficiently fast (e.g. driver
   * is in main memory, or driver does caching itself).
   */
  public boolean cachingMakesSense()
  {
      return true;
  }

  public void close()
    throws SQLException
  {
      try {
	  //m_vdc.close();
	  //m_db.close();
      } catch (Exception e){
	  throw new SQLException(e.getMessage());
      }
  }
}
